﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using FTD2XX_NET;

namespace Innovox.IO.Acquisition
{
    public enum Dlpio8gCommands
    {
        Ping = 0x27
    }

    public class DLPIO8G
    {
	    FTDI _ftdiDevice;
	    uint _devicesCount;
	    FTDI.FT_DEVICE_INFO_NODE _connectedNode;

        // Allocate storage for device info list
		FTDI.FT_DEVICE_INFO_NODE[] _ftdiDeviceList;

		public DLPIO8G()
		{
        	InitDevice();
		}

		private void InitDevice()
		{
			// Create new instance of the FTDI device class
			_ftdiDevice = new FTDI();
		}

		public bool IsConnected()
		{
			return _ftdiDevice.IsOpen;
		}

		public void Close()
		{
		    _connectedNode = null;
		    _ftdiDevice.Close();
		}

		public bool Open()
		{
			_connectedNode = null;

			if (_ftdiDevice.GetNumberOfDevices(ref _devicesCount) != FTDI.FT_STATUS.FT_OK)
                throw (new Exception("Error while accessing USB port"));

			if (_devicesCount == 0)
                throw (new Exception("DLP-IO8-G not connected"));

			_ftdiDeviceList = new FTDI.FT_DEVICE_INFO_NODE[_devicesCount];

            if (_ftdiDevice.GetDeviceList(_ftdiDeviceList) != FTDI.FT_STATUS.FT_OK)
					throw (new Exception("Error while accessing USB port"));
			
            byte[] rxBuf = new byte[10];

			foreach (FTDI.FT_DEVICE_INFO_NODE n in _ftdiDeviceList)
			{
				if (n.Type == FTDI.FT_DEVICE.FT_DEVICE_232R)
				{
                    _ftdiDevice.OpenBySerialNumber(n.SerialNumber);

                    // Set up device data parameters
                    if (_ftdiDevice.SetBaudRate(115200) != FTDI.FT_STATUS.FT_OK)
                        continue;

                    if (_ftdiDevice.SetDataCharacteristics(FTDI.FT_DATA_BITS.FT_BITS_8,
                                                            FTDI.FT_STOP_BITS.FT_STOP_BITS_1,
                                                            FTDI.FT_PARITY.FT_PARITY_NONE) != FTDI.FT_STATUS.FT_OK)
                        continue;

                    if (_ftdiDevice.SetTimeouts(300, 0) != FTDI.FT_STATUS.FT_OK)
                        continue;

                    Thread.Sleep(300);
                    
                    uint numBytesRead = Send((byte)Dlpio8gCommands.Ping, ref rxBuf);

                    if (numBytesRead == 1 && rxBuf[0] == 0x51)
                    {
                        _connectedNode = n;
                        break;
                    }
				}
			}

            if (_connectedNode == null)
                return false;

			return _ftdiDevice.IsOpen;
		}

        private uint Send(byte cmd, ref byte[] rxBuf)
		{
			if( _ftdiDevice.IsOpen == false )
				throw(new Exception("DLP-IO8-G not connected"));

			uint numBytes = 0;
			byte[] txBuf = new byte[1] { cmd };
			_ftdiDevice.Purge(FTDI.FT_PURGE.FT_PURGE_RX);

            if (_ftdiDevice.Write(txBuf, (int)txBuf.Length, ref numBytes) != FTDI.FT_STATUS.FT_OK)
				throw(new Exception("Error while writing data to DLP-IO8-G"));

            if (rxBuf.Length > 0)
			    if (_ftdiDevice.Read(rxBuf, (uint)rxBuf.Length, ref numBytes) != FTDI.FT_STATUS.FT_OK)
                    throw (new Exception("Error while reading data from DLP-IO8-G"));

            return numBytes;
		}

        static char[] DigOutHighCmd = new char[]{ '1', '2', '3', '4', '5', '6', '7', '8' };
        static char[] DigOutLowCmd = new char[] { 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I' };
        static char[] DigInCmd = new char[]     { 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K' };
        static char[] AnalogInCmd = new char[]  { 'Z', 'X', 'C', 'V', 'B', 'N', 'M', ',' };

        public void WriteDigOut(byte ch, bool state)
        {
            byte cmd;

            if (ch > 0 && ch <= 8)
            {
                if (state)
                    cmd = (byte)DigOutHighCmd[ch - 1];
                else
                    cmd = (byte)DigOutLowCmd[ch - 1];

                byte[] rxBuf = new byte[0];

                Send(cmd, ref rxBuf);
            }
        }

        public bool ReadDigIn(byte ch)
        {
            bool state;

            if (ch > 0 && ch <= 8)
            {
                byte cmd;
                cmd = (byte)DigInCmd[ch - 1];

                byte[] rxBuf = new byte[1];

                if (Send(cmd, ref rxBuf) == 1)
                {
                    if (rxBuf[0] == '0')
                        state = false;
                    else if (rxBuf[0] == '1')
                        state = true;
                    else
                        throw (new Exception("DLP-IO8-G Digital input read error"));
                }
                else
                    throw (new Exception("DLP-IO8-G Digital input read error"));
            }
            else
                throw (new Exception("DLP-IO8-G channel is out of range"));

            return state;
        }

        public float ReadAnalogIn(byte ch)
        {
            string voltage = "";

            if (ch > 0 && ch <= 8)
            {
                byte cmd;
                cmd = (byte)AnalogInCmd[ch - 1];

                byte[] rxBuf = new byte[10];

                if (Send(cmd, ref rxBuf) > 0)
                    voltage = System.Text.ASCIIEncoding.ASCII.GetString(rxBuf);
                else
                    throw (new Exception("DLP-IO8-G Analog input read error"));
            }
            else
                throw (new Exception("DLP-IO8-G channel is out of range"));

            float v = 0;
            int i = voltage.IndexOf('V');

            string str = voltage.Substring(0, i);

            if( float.TryParse(str,out v ) == false )
                throw (new Exception("DLP-IO8-G Analog input read error"));

            return v;
        }

    }
}
